/*sdio.c begin*/
#include "mmc_core.h"

static int sdio_read_fbr(struct sdio_func *fc)
{
    int ret = 0;
    uint8_t tmp_data;
    uint32_t fbr_addr = 0;

    fbr_addr = SDIO_FBR_BASE(fc->func_num) + SDIO_FBR_STD_FUNC_IF_C;
    ret = sdio_rw_direct(fc->card, 0, 0, fbr_addr, 0, &tmp_data);
    if (ret)
        goto out;

    tmp_data &= 0x0f;

    if (tmp_data == 0x0f) {
        fbr_addr = SDIO_FBR_BASE(fc->func_num) + SDIO_FBR_STD_IF_EXT;
        ret = sdio_rw_direct(fc->card, 0, 0, fbr_addr, 0, &tmp_data);
        if (ret)
            goto out;
    }

    fc->func_class = tmp_data;

out:
    return ret;
}

struct sdio_func *sdio_alloc_func(struct mmc_card *card)
{
    struct sdio_func *func = NULL;

    func = malloc(sizeof(struct sdio_func));
    if (func == NULL)
        return NULL;

    (void)memset_s(func, sizeof(struct sdio_func), 0, sizeof(struct sdio_func));
    func->card = card;

    return func;
}

static int sdio_init_func(struct mmc_card *card, uint32_t fn)
{
    int ret = 0;
    struct sdio_func *fc = NULL;

    fc = sdio_alloc_func(card);
    if (fc == NULL)
        return -ENOMEM;
    fc->func_num = fn;

    if (!(is_card_not_std_sdio(card))) {
        ret = sdio_read_fbr(fc);
        if (ret)
            goto fail;

        ret = sdio_read_func_cis(fc);
        if (ret)
            goto fail;
    } else {
        fc->manufacturer_id = fc->card->card_reg.cis.vendor;
        fc->max_blk_size = fc->card->card_reg.cis.blksize;
        fc->device_id = fc->card->card_reg.cis.device;
    }

    card->sdio_funcs[fn - 1] = fc;

    mmc_printf(MMC_TRACE_ERR, "SDIO card vendor=0x%x,device=0x%x\n",
                 fc->manufacturer_id, fc->device_id);
    return 0;

fail:
    if (fc != NULL)
        free(fc);
    return ret;
}

static int sdio_read_cccr(struct mmc_card *card)
{
    int ret;
    int vsn;
    unsigned char tmp_data;

    (void)memset_s(&card->card_reg.cccr, sizeof(struct sdio_card_cccr), 0, sizeof(struct sdio_card_cccr));

    ret = sdio_rw_direct(card, 0, 0, SDIO_CCCR, 0, &tmp_data);
    if (ret)
        goto out;

    vsn = tmp_data & 0x0f;

    if (vsn > SDIO_CCCR_REV_3_00) {
        mmc_printf(MMC_PRINTF_WARN, "unknown CCCR structure version %d\n",
            vsn);
        return -EINVAL;
    }
    card->card_reg.cccr.sdio_vsn = (tmp_data & 0xf0) >> 4;

    ret = sdio_rw_direct(card, 0, 0, SDIO_CCCR_CAPS, 0, &tmp_data);
    if (ret)
        goto out;

    if (tmp_data & SDIO_CCCR_CAP_4BLS)
        card->card_reg.cccr.sdio_wide_bus = 1;
    if (tmp_data & SDIO_CCCR_CAP_LSC)
        card->card_reg.cccr.sdio_low_speed = 1;
    if (tmp_data & SDIO_CCCR_CAP_SMB)
        card->card_reg.cccr.multi_blk = 1;

    if (vsn >= SDIO_CCCR_REV_1_10) {
        ret = sdio_rw_direct(card, 0, 0, SDIO_CCCR_POWER_CTL, 0, &tmp_data);
        if (ret)
            goto out;

        if (tmp_data & SDIO_POWER_SMPC)
            card->card_reg.cccr.sdio_high_power = 1;
    }

    if (vsn >= SDIO_CCCR_REV_2_00) {
        ret = sdio_rw_direct(card, 0, 0, SDIO_CCCR_SPEED_SEL, 0, &tmp_data);
        if (ret)
            goto out;

        if (tmp_data & SDIO_SPEED_SHS) {
            card->card_reg.cccr.sdio_high_speed = 1;
        }
    }

out:
    return ret;
}

static int sdio_enable_wide(struct mmc_card *cur_card)
{
    int error;
    unsigned char ctrl;

    if (!(cur_card->host->caps.bits.cap_4_bit))
        return 0;

    if (cur_card->card_reg.cccr.sdio_low_speed && !cur_card->card_reg.cccr.sdio_wide_bus)
        return 0;

    error = sdio_rw_direct(cur_card, 0, 0, SDIO_CCCR_BUS_IF_C, 0, &ctrl);
    if (error)
        return error;

    ctrl |= SDIO_CCCR_WIDTH_4BIT;

    error = sdio_rw_direct(cur_card, 1, 0, SDIO_CCCR_BUS_IF_C, ctrl, NULL);
    if (error)
        return error;

    return 1;
}

static int sdio_disable_cd(struct mmc_card *cur_card)
{
    int error;
    unsigned char ctrl;

    error = sdio_rw_direct(cur_card, 0, 0, SDIO_CCCR_BUS_IF_C, 0, &ctrl);
    if (error)
        return error;

    ctrl |= SDIO_CCCR_CD_DISABLE;

    return sdio_rw_direct(cur_card, 1, 0, SDIO_CCCR_BUS_IF_C, ctrl, NULL);
}

static int sdio_enable_4bit_bus(struct mmc_card *cur_card)
{
    int error = 0;

    if (is_card_sdio(cur_card))
        return sdio_enable_wide(cur_card);

    if ((cur_card->host->caps.bits.cap_4_bit) &&
            (cur_card->card_reg.scr.bus_widths & SD_SCR_BUS_WIDTH_4)) {
        error = sd_app_set_bus_width(cur_card, BUS_WIDTH_4);
        if (error)
            return error;
    } else
        return 0;
    error = sdio_enable_wide(cur_card);
    if (error <= 0)
        (void)sd_app_set_bus_width(cur_card, BUS_WIDTH_1);
    return error;
}

static int sdio_switch_hs(struct mmc_card *card, int enable)
{
    int error = 0;
    unsigned char speed_mode;

    if (!(card->host->caps.bits.cap_sd_highspeed))
        return error;

    if (!card->card_reg.cccr.sdio_high_speed)
        return error;

    error = sdio_rw_direct(card, 0, 0, SDIO_CCCR_SPEED_SEL, 0, &speed_mode);
    if (error)
        return error;

    if (enable)
        speed_mode |= SDIO_SPEED_EHS;
    else
        speed_mode &= ~SDIO_SPEED_EHS;

    error = sdio_rw_direct(card, 1, 0, SDIO_CCCR_SPEED_SEL, speed_mode, NULL);
    if (error)
        return error;

    return 1;
}

static int sdio_enable_hs(struct mmc_card *cur_card)
{
    int error;

    error = sdio_switch_hs(cur_card, TRUE);
    if (is_card_sdio(cur_card) || (error <= 0) )
        return error;

    error = sd_switch_func_hs(cur_card);
    if (error <= 0)
        (void)sdio_switch_hs(cur_card, FALSE);

    return error;
}

static uint32_t mmc_sdio_get_max_clock(struct mmc_card *card)
{
    uint32_t max;

    if (is_card_highspeed(card))
        max = 50000000;
    else
        max = card->card_reg.cis.max_dtr;

    if (is_card_combo(card))
        max = min(max, mmc_sd_get_max_clock(card));/*lint !e666*/

    return max;
}

static void mmc_sdio_remove(struct mmc_card *card)
{
    uint32_t i = 0;

    while(i < card->sdio_func_num) {
        if (card->sdio_funcs[i]) {
            free(card->sdio_funcs[i]);
            card->sdio_funcs[i] = NULL;
        }
        i++;
    }
}

int sdio_reset(struct mmc_card *card)
{
    int error = 0;
    uint8_t out = 0;
    error = sdio_io_rw_direct(card, 0, 0, SDIO_CCCR_ASx, 0, &out);
    if (error)
        out = 0x08;
    else
        out |= 0x08;
    error = sdio_io_rw_direct(card, 1, 0, SDIO_CCCR_ASx, out, NULL);
    return error;
}

int sdio_reset_comm(struct mmc_card *cur_card)
{
    struct mmc_host *host = cur_card->host;
    uint32_t ocr;
    int error;
    mmc_idle_card(cur_card);
    mmc_set_clock(host, host->freq_min);
    error = sdio_io_send_op_cond(cur_card, 0, &ocr);
    if (error)
        goto err;
    error = sdio_io_send_op_cond(cur_card, cur_card->card_reg.ocr.ocr_data, &ocr);
    if (error)
        goto err;
    error = sd_send_relative_addr(cur_card, &cur_card->card_reg.rca);
    if (error)
        goto err;
    error = mmc_select_card(cur_card);
    if (error)
        goto err;
    error = sdio_enable_hs(cur_card);
    if (error > 0)
        sd_go_highspeed(cur_card);
    else if (error)
        goto err;
    mmc_set_clock(host, mmc_sdio_get_max_clock(cur_card));
    error = sdio_enable_4bit_bus(cur_card);
    if (error > 0)
        mmc_set_bus_width(host, BUS_WIDTH_4);
    else if (error)
        goto err;
    return 0;
err:
    mmc_printf(MMC_PRINTF_ERR, "reset SDIO comm,error:(%d)\n",
            error);
    return error;
}


int mmc_assume_sdio(struct mmc_card *card)
{
    int error, i, func_num;
    union mmc_ocr ocr;
    union mmc_ocr ocr_cid;
    struct mmc_host *host = NULL;
    mmc_assert(card);

    host = card->host;
    error = sdio_io_send_op_cond(card, 0, &ocr.ocr_data);
    if (error)
        return error;
    if (ocr.ocr_data & 0x7F) {
        mmc_printf(MMC_PRINTF_WARN,  "card claim to support voltages "
                "below the defined range. be ignored.\n");
        ocr.ocr_data &= ~0x7F;
    }

    card->card_reg.ocr.ocr_data = mmc_select_voltage(host, ocr.ocr_data);

    error = sdio_io_send_op_cond(card, card->card_reg.ocr.ocr_data, &ocr.ocr_data);
    if (error)
        goto error;

    /* identify card */
    ocr_cid.ocr_data = (card->card_reg.ocr.ocr_data) & (ocr.ocr_data);
    if ((sd_get_cid(card, ocr_cid,  NULL) == 0 )
            && (ocr.ocr_data & SDIO_R4_MEMORY_PRESENT))
        card->type = CARD_TYPE_COMBO;
    else
        card->type = CARD_TYPE_SDIO;

    error = sd_send_relative_addr(card, &card->card_reg.rca);
    if (error)
        goto error;

    if (is_card_combo(card)) {
        error = sd_get_csd(card);
        if (error)
            goto error;
        sd_decode_cid(card);
    }

    error = mmc_select_card(card);
    if (error)
        goto error;

    if (is_card_not_std_sdio(card)) {
        card->iocfg.clock = card->card_reg.cis.max_dtr;
        mmc_set_clock(host, card->iocfg.clock);

        if(card->card_reg.cccr.sdio_high_speed) {
            set_card_highspeed(card);
        }
    } else {
        error = sdio_read_cccr(card);
        if (error)
            goto error;
        error = sdio_read_cis(card, NULL);
        if (error)
            goto error;
        if (is_card_combo(card)) {
            error = sd_setup_card(card);
            if (error) {
                mmc_idle_card(card);
                card->type = CARD_TYPE_SDIO;
            }
        }
        error = sdio_disable_cd(card);
        if (error)
            goto error;

        error = sdio_enable_hs(card);
        if (error > 0)
            sd_go_highspeed(card);
        else if (error)
            goto error;

        card->iocfg.clock = mmc_sdio_get_max_clock(card);
        mmc_set_clock(host, card->iocfg.clock);

        error = sdio_enable_4bit_bus(card);
        if (error > 0) {
            card->iocfg.bus_width = BUS_WIDTH_4;
            mmc_set_bus_width(card->host, card->iocfg.bus_width);
        }
        else if (error)
            goto error;
    }
    func_num = (ocr.ocr_data >> 28) & 7;
    card->sdio_func_num = 0;
    for (i = 0; i < func_num; i++, card->sdio_func_num++) {
        error = sdio_init_func(card, i + 1);
        if (error)
            goto remove_func;
    }
    error = mmc_add_card(card);
    if (error)
        goto remove_func;
    return 0;

remove_func:
    mmc_sdio_remove(card);
error:
    mmc_reset_card(card);
    mmc_printf(MMC_PRINTF_ERR, ": error %d initializing SDIO card\n",
            error);
    return error;
}

/* sdio.c */
